<template>
    <el-form ref="form" :id="id" :label-width="labelWidth" :label-position="vertical ? 'top' : 'right'"
        :model="model" :rules="validationRules" :validate-on-rule-change="false"
        :inline="inline" :inline-message="!vertical" :disabled="disabled"
        :class="theme ? ('theme-' + theme) : null" :size="size" :status-icon="statusIcon">
        <slot></slot>
        <el-form-item class="w-100 mb-0" :label="vertical ? undefined : '&nbsp;'"
            v-if="submit !== undefined && submit !== null">
            <el-button :type="theme || 'primary'" :size="size" @click="toSubmit" v-if="submit !== false">
                {{ _submitText }}
            </el-button>
            <el-button :size="size" @click="toCancel" v-if="cancel !== false">{{ cancelText }}</el-button>
        </el-form-item>
    </el-form>
</template>

<script>
import $ from 'cash-dom';
import AsyncValidator from 'async-validator';

export default {
    name: 'TnxelSubmitForm',
    props: {
        /**
         * 所属滚动容器的选择器
         */
        container: {
            type: String,
            default: 'main',
        },
        model: {
            type: Object,
            required: true,
        },
        rules: [String, Object], // 加载字段校验规则的URL地址，或规则集对象
        rulesApp: { // 加载字段校验规则的应用名称
            type: String,
            default: () => window.tnx.componentDefaultApp, // 设置为方法以延时加载，确保更改的值生效
        },
        rulesLoaded: Function, // 规则集加载后的附加处理函数，仅在rules为字符串类型的URL地址时有效
        submit: {
            type: [Function, Boolean],
            default: null,
        },
        submitText: String,
        theme: String,
        cancel: {
            type: [String, Function, Boolean],
            default: true
        },
        cancelText: {
            type: String,
            default: '取消'
        },
        vertical: {
            type: Boolean,
            default: false
        },
        inline: {
            type: Boolean,
            default: false
        },
        errorFocus: {
            type: Boolean,
            default: false,
        },
        labelWidth: {
            type: [String, Number],
            default: 'auto',
        },
        size: String,
        statusIcon: {
            type: Boolean,
            default: false,
        },
    },
    emits: ['rules-loaded', 'meta'],
    data() {
        return {
            id: window.tnx.util.string.random(32),
            validationRules: {},
            disabled: false,
            topOffset: 0,
            fieldNames: [],
        };
    },
    computed: {
        _submitText() {
            if (this.submitText) {
                return this.submitText;
            }
            return this.cancel === false ? '保存' : '提交';
        }
    },
    watch: {
        rules() {
            this.initRules();
        },
    },
    mounted() {
        this.initRules();

        this.$nextTick(() => {
            let container = $(this.container);
            if (container.length) {
                this.topOffset = $('#' + this.id).offset().top - container.offset().top - 16;
            }
        });
    },
    methods: {
        initRules() {
            if (this.rules) {
                if (typeof this.rules === 'string') {
                    window.tnx.app.rpc.getMeta(this.rules, this.rulesApp).then(meta => {
                        if (this.rulesLoaded) {
                            this.rulesLoaded(meta.$rules);
                        } else {
                            this.$emit('rules-loaded', meta.$rules);
                        }
                        this.validationRules = meta.$rules;
                        delete meta.$rules;
                        this.$emit('meta', meta);
                        this.fieldNames = Object.keys(meta);
                    });
                } else {
                    this.validationRules = {};
                    Object.keys(this.rules).forEach(fieldName => {
                        let fieldRules = this.rules[fieldName];
                        if (fieldRules && Array.isArray(fieldRules)) {
                            this.validationRules[fieldName] = fieldRules.filter((rule, index) => {
                                let valid = rule !== undefined && rule !== null;
                                if (!valid) {
                                    console.error(
                                        'invalid rules[' + index + '] for field "' + fieldName + '": ' + rule);
                                }
                                return valid;
                            });
                        } else {
                            console.error('invalid rules for field "' + fieldName + '": ' + fieldRules);
                        }
                    });
                }
            } else {
                this.validationRules = {};
            }
        },
        disable(disabled) {
            this.disabled = disabled !== false;
        },
        focusError(errors) {
            let $form = $('#' + this.id);
            let fieldNames = Object.keys(errors);
            let fieldName = fieldNames[0];
            if (fieldName) {
                let $item = $('.el-form-item label[for=' + fieldName + ']', $form).parents('.el-form-item');
                if ($item.length) {
                    let $input = $('input:first', $item);
                    if ($input.length) {
                        $input.focus();
                        return;
                    }
                }
                // 没有找到错误字段输入框，则滚动到错误栏目处
                this.$refs.form.scrollToField(fieldName);
            }
        },
        validate() {
            return new Promise((resolve) => {
                if (this.$refs.form) {
                    this.$refs.form.validate((valid) => {
                        resolve(valid);
                    });
                } else if (this.rules) {
                    const validator = new AsyncValidator(this.rules);
                    validator.validate(this.model).then(() => {
                        resolve(true);
                    }).catch(() => {
                        resolve(false);
                    });
                } else {
                    resolve(true);
                }
            });
        },
        validateField(props, callback, errorFocus) {
            let _this = this;
            this.$refs.form.validateField(props, (errorMessage) => {
                if (errorMessage && _this.errorFocus && errorFocus !== false) {
                    _this.$nextTick(() => {
                        _this.focusError.call(_this);
                    });
                }
                if (typeof callback === 'function') {
                    callback(errorMessage);
                }
            });
        },
        clearValidate(props) {
            this.$refs.form.clearValidate(props);
        },
        toSubmit(callback, disabled) {
            this.validate().then(success => {
                if (success) {
                    if (typeof callback !== 'function') {
                        callback = this.submit;
                    }
                    if (typeof callback === 'function') {
                        if (disabled !== false) {
                            this.disable();
                        }
                        callback(this);
                    }
                }
            });
        },
        toCancel() {
            if (typeof this.cancel === 'function') {
                this.cancel();
            } else if (typeof this.cancel === 'string') {
                this.$router.back(this.cancel);
            } else if (this.cancel !== false) {
                this.$router.back();
            }
        },
        getFieldNames() {
            return this.fieldNames;
        },
        /**
         * 执行所有的校验规则，以自定义方式处理错误
         * @param fieldLabels 字段标签，可以是一个函数，也可以是一个对象，用于自定义错误消息中的字段标签
         * @returns Promise<string[]|null> 错误消息字符串数组，没有错误时为null
         */
        validateRules(fieldLabels) {
            let validator = new AsyncValidator(this.validationRules);
            return validator.validate(this.model).then(() => {
                return null;
            }).catch((errors) => {
                let messages = [];
                if (errors) {
                    for (let error of errors) {
                        let fieldLabel = undefined;
                        if (typeof fieldLabels === 'function') {
                            fieldLabel = fieldLabels(error.field);
                        } else if (typeof fieldLabels === 'object') {
                            fieldLabel = fieldLabels[error.field];
                        }
                        fieldLabel = fieldLabel || error.field;
                        messages.push(fieldLabel + error.message);
                    }
                }
                return messages.length === 0 ? null : messages;
            });
        }
    }
}
</script>
